/**
 * 
 */
package com.example.demo.aspect;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.alibaba.fastjson.JSON;
import com.example.demo.annotation.NoRepeatSubmit;
import com.example.demo.utils.ApiResult;
import com.example.demo.utils.RedisLock;
import com.example.demo.utils.RequestUtils;
import com.google.common.cache.Cache;

import lombok.extern.slf4j.Slf4j;

/**
 * @author xiazhengjiao
 * @date 2019年5月14日
 */
@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAspect {

	@Autowired
	private Cache<String, Integer> cache;

	@Autowired
	private RedisLock redisLock;

	@Pointcut("@annotation(noRepeatSubmit)")
	public void pointCut(NoRepeatSubmit noRepeatSubmit) {
	}

	@Around("pointCut(noRepeatSubmit)")
	public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
		int lockSeconds = noRepeatSubmit.lockTime();

		HttpServletRequest request = RequestUtils.getRequest();
		Assert.notNull(request, "request can not null");

		// 此处可以用token或者JSessionId
		String token = request.getHeader("SubmitToken");
		String path = request.getServletPath();
		String key = getKey(token, path);
		String clientId = getClientId();

		log.info("tryLock key = [{}], clientId = [{}]", key, clientId);
		//return this.cacheLock(key, pjp);
		return this.redisLock(key, clientId, lockSeconds, pjp);

	}

	/**
	 *负载均衡 redis锁不支持redis集群
	 * @param key
	 * @param clientId
	 * @param lockSeconds
	 * @param pjp
	 * @return
	 * @throws Throwable
	 */
	private Object redisLock(String key, String clientId, Integer lockSeconds, ProceedingJoinPoint pjp)
			throws Throwable {
		boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);
		log.info("tryLock key = [{}], clientId = [{}]", key, clientId);

		if (isSuccess) {
			log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
			// 获取锁成功
			Object result;

			try {
				// 执行进程
				result = pjp.proceed();
				log.info("执行进程:"+JSON.toJSONString(result));
			} finally {
				// 解锁
				//redisLock.releaseLock(key, clientId);
				log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
			}

			return result;

		} else {
			// 获取锁失败，认为是重复提交的请求
			log.info("tryLock fail, key = [{}]", key);
			return new ApiResult(200, "重复请求，请稍后再试", null);
		}
	}

	/**
	 * 本地缓存
	 * @param key
	 * @param pjp
	 * @return
	 * @throws Throwable
	 */
	private Object cacheLock(String key, ProceedingJoinPoint pjp) throws Throwable {
		if (cache.getIfPresent(key) == null) {// 如果缓存中有这个url视为重复提交
			Object o = pjp.proceed();
			cache.put(key, 0);
			return o;
		} else {
			log.error("重复提交");
			return new ApiResult(200, "重复请求，请稍后再试", null);
		}
	}

	private String getKey(String token, String path) {
		return token + path;
	}

	private String getClientId() {
		return UUID.randomUUID().toString();
	}
}
